/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-2025 Ta4j Organization & respective
 * authors (see AUTHORS)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package ta4jexamples.backtesting;

import java.awt.GraphicsEnvironment;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jfree.chart.JFreeChart;
import org.ta4j.core.AnalysisCriterion;
import org.ta4j.core.AnalysisCriterion.PositionFilter;
import org.ta4j.core.BarSeries;
import org.ta4j.core.BaseStrategy;
import org.ta4j.core.Rule;
import org.ta4j.core.Strategy;
import org.ta4j.core.TradingRecord;
import org.ta4j.core.backtest.BarSeriesManager;
import org.ta4j.core.criteria.ExpectancyCriterion;
import org.ta4j.core.criteria.PositionsRatioCriterion;
import org.ta4j.core.criteria.SqnCriterion;
import org.ta4j.core.criteria.drawdown.MaximumDrawdownCriterion;
import org.ta4j.core.criteria.drawdown.ReturnOverMaxDrawdownCriterion;
import org.ta4j.core.criteria.VersusEnterAndHoldCriterion;
import org.ta4j.core.criteria.pnl.NetProfitLossCriterion;
import org.ta4j.core.criteria.pnl.NetReturnCriterion;
import org.ta4j.core.indicators.RSIIndicator;
import org.ta4j.core.indicators.averages.SMAIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsLowerIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsMiddleIndicator;
import org.ta4j.core.indicators.bollinger.BollingerBandsUpperIndicator;
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.indicators.helpers.PreviousValueIndicator;
import org.ta4j.core.indicators.statistics.StandardDeviationIndicator;
import org.ta4j.core.indicators.volume.OnBalanceVolumeIndicator;
import org.ta4j.core.num.Num;
import org.ta4j.core.rules.AverageTrueRangeStopLossRule;
import org.ta4j.core.rules.CrossedDownIndicatorRule;
import org.ta4j.core.rules.CrossedUpIndicatorRule;
import org.ta4j.core.rules.OverIndicatorRule;
import org.ta4j.core.rules.UnderIndicatorRule;
import ta4jexamples.charting.workflow.ChartWorkflow;
import ta4jexamples.datasources.YahooFinanceHttpBarSeriesDataSource;

/**
 * Yahoo Finance Data Source Backtest - Advanced Multi-Indicator Strategy
 * <p>
 * This example demonstrates advanced ta4j features beyond the Quickstart:
 * <ul>
 * <li>Loading historical OHLCV data from Yahoo Finance API</li>
 * <li>Bollinger Bands for mean reversion signals</li>
 * <li>ATR-based dynamic stop-loss (adapts to market volatility)</li>
 * <li>RSI for momentum confirmation</li>
 * <li>Volume analysis with On-Balance Volume (OBV)</li>
 * <li>Indicator composition using BinaryOperationIndicator</li>
 * <li>Advanced performance metrics (Expectancy, SQN, Maximum Drawdown)</li>
 * <li>Multi-subchart visualization</li>
 * </ul>
 * <p>
 * <strong>Strategy Concept:</strong> A mean reversion strategy that buys when
 * price touches the lower Bollinger Band (oversold) with RSI confirmation and
 * volume support, using ATR-based stops that adapt to market volatility.
 * <p>
 * <strong>Data Source:</strong> This example uses Yahoo Finance's public API to
 * fetch real market data. No API key is required, but be aware of rate limits
 * (~2000 requests/hour per IP).
 * <p>
 * Run this example to see an advanced trading strategy backtested on real
 * market data with comprehensive analysis!
 */
public class YahooFinanceBacktest {

    private static final Logger LOG = LogManager.getLogger(YahooFinanceBacktest.class);

    public static void main(String[] args) {
        System.out.println("╔══════════════════════════════════════════════════════════════╗");
        System.out.println("║     Yahoo Finance Data Source - Backtesting Example         ║");
        System.out.println("╚══════════════════════════════════════════════════════════════╝");
        System.out.println();

        // Step 1: Load historical price data from Yahoo Finance
        System.out.println("[1/7] Loading historical price data from Yahoo Finance...");
        System.out.println("   Fetching 2 years of daily data for Apple Inc. (AAPL)...");
        System.out.println("   (More data = better indicator calculations)");

        // Load 2 years of data for better indicator stability
        YahooFinanceHttpBarSeriesDataSource dataSource = new YahooFinanceHttpBarSeriesDataSource(true);
        BarSeries series = dataSource.loadSeriesInstance("AAPL",
                YahooFinanceHttpBarSeriesDataSource.YahooFinanceInterval.DAY_1, 730);
        // Alternative methods you can try:
        // BarSeries series = dataSource.loadSeriesInstance("AAPL",
        // YahooFinanceInterval.DAY_1, 500); // 500 bars
        // BarSeries series = dataSource.loadSeriesInstance("MSFT",
        // YahooFinanceInterval.HOUR_1, 1000); // Hourly data
        // BarSeries series = dataSource.loadSeriesInstance("BTC-USD",
        // YahooFinanceInterval.DAY_1,
        // Instant.parse("2023-01-01T00:00:00Z"),
        // Instant.parse("2023-12-31T23:59:59Z")); // Date range

        if (series == null || series.getBarCount() == 0) {
            System.err.println("   [ERROR] Failed to load data from Yahoo Finance");
            System.err.println("   [TIP] Check your internet connection and try again");
            System.err.println("   [TIP] Yahoo Finance may have rate limits - wait a few minutes and retry");
            return;
        }

        System.out.printf("   [OK] Loaded %d bars of price data%n", series.getBarCount());
        System.out.printf("   [INFO] Date range: %s to %s%n", series.getFirstBar().getEndTime(),
                series.getLastBar().getEndTime());
        System.out.println();

        // Step 2: Create base indicators
        System.out.println("[2/7] Creating technical indicators...");
        ClosePriceIndicator closePrice = new ClosePriceIndicator(series);

        // Bollinger Bands: Mean reversion indicator
        // Uses 20-period SMA with 2 standard deviations
        int bbPeriod = 20;
        double bbMultiplier = 2.0;
        SMAIndicator bbSma = new SMAIndicator(closePrice, bbPeriod);
        StandardDeviationIndicator bbStdDev = new StandardDeviationIndicator(closePrice, bbPeriod);
        BollingerBandsMiddleIndicator bbMiddle = new BollingerBandsMiddleIndicator(bbSma);
        BollingerBandsUpperIndicator bbUpper = new BollingerBandsUpperIndicator(bbMiddle, bbStdDev,
                series.numFactory().numOf(bbMultiplier));
        BollingerBandsLowerIndicator bbLower = new BollingerBandsLowerIndicator(bbMiddle, bbStdDev,
                series.numFactory().numOf(bbMultiplier));

        // RSI: Momentum oscillator (14-period)
        RSIIndicator rsi = new RSIIndicator(closePrice, 14);

        // On-Balance Volume: Volume-based trend indicator
        OnBalanceVolumeIndicator obv = new OnBalanceVolumeIndicator(series);

        // Note: ATR is used in the AverageTrueRangeStopLossRule below
        // Advanced: You can also create custom indicators using
        // BinaryOperationIndicator
        // Example: Calculate distance from price to middle band as a percentage
        // BinaryOperationIndicator priceToMiddleRatio =
        // BinaryOperationIndicator.quotient(
        // BinaryOperationIndicator.difference(closePrice, bbMiddle), bbMiddle);

        System.out.println("   [OK] Created Bollinger Bands (20-period, 2 std dev)");
        System.out.println("   [OK] Created RSI (14-period)");
        System.out.println("   [OK] Created ATR (14-period) for dynamic stops");
        System.out.println("   [OK] Created On-Balance Volume indicator");
        System.out.println("   [OK] Created custom price-to-middle-band ratio indicator");
        System.out.println();

        // Step 3: Build advanced trading rules
        System.out.println("[3/7] Building advanced trading strategy rules...");
        System.out.println("   Strategy: Mean reversion with multiple confirmations");

        // Entry rule: Buy when price is at or below lower Bollinger Band (oversold)
        // AND RSI is below 45 (oversold confirmation - less strict than 40)
        // Price touching lower BB OR crossing below it
        Rule priceAtLowerBB = new UnderIndicatorRule(closePrice, bbLower)
                .or(new CrossedDownIndicatorRule(closePrice, bbLower));
        Rule rsiOversold = new UnderIndicatorRule(rsi, series.numFactory().numOf(45));
        // Optional: OBV rising provides additional confirmation (but not required)
        // This makes the strategy more tradeable while still using volume analysis
        Rule obvRising = new OverIndicatorRule(obv, new PreviousValueIndicator(obv, 1));

        // Entry: Price at lower BB + RSI oversold + (OBV rising OR price below middle
        // band)
        // This allows entries when either volume confirms OR price is clearly oversold
        Rule priceBelowMiddle = new UnderIndicatorRule(closePrice, bbMiddle);
        Rule buyingRule = priceAtLowerBB.and(rsiOversold).and(obvRising.or(priceBelowMiddle));

        // Exit rule: Sell when price reaches upper Bollinger Band (overbought)
        // OR RSI crosses above 65 (overbought - less strict than 70 for more exits)
        // OR ATR-based stop loss triggers (dynamic, adapts to volatility)
        Rule exitCondition1 = new CrossedUpIndicatorRule(closePrice, bbUpper)
                .or(new OverIndicatorRule(closePrice, bbUpper));
        Rule exitCondition2 = new OverIndicatorRule(rsi, series.numFactory().numOf(65));
        // ATR-based stop: 2.5x ATR below entry price (allows for some volatility)
        Rule exitCondition3 = new AverageTrueRangeStopLossRule(series, 14, 2.5);

        Rule sellingRule = exitCondition1.or(exitCondition2).or(exitCondition3);

        Strategy strategy = new BaseStrategy("Bollinger Bands Mean Reversion (Multi-Confirm)", buyingRule, sellingRule);
        System.out.println("   [OK] Entry: Price at/below lower BB + RSI < 45 + (OBV rising OR price below middle)");
        System.out.println("   [OK] Exit: Price at/above upper BB OR RSI > 65 OR ATR stop (2.5x ATR)");
        System.out.println();

        // Step 4: Run backtest
        System.out.println("[4/7] Running backtest on historical data...");
        BarSeriesManager seriesManager = new BarSeriesManager(series);
        TradingRecord tradingRecord = seriesManager.run(strategy);
        System.out.printf("   [OK] Backtest complete: %d positions executed%n", tradingRecord.getPositionCount());
        System.out.println();

        // Step 5: Advanced performance analysis
        System.out.println("[5/7] Advanced Performance Analysis");
        System.out.println("   ──────────────────────────────────────────");

        // Basic metrics
        AnalysisCriterion netReturn = new NetReturnCriterion();
        AnalysisCriterion winningPositionsRatio = new PositionsRatioCriterion(PositionFilter.PROFIT);
        Num netReturnValue = netReturn.calculate(series, tradingRecord);
        Num winRate = winningPositionsRatio.calculate(series, tradingRecord);

        // Advanced risk-adjusted metrics
        AnalysisCriterion romad = new ReturnOverMaxDrawdownCriterion();
        AnalysisCriterion maxDrawdown = new MaximumDrawdownCriterion();
        AnalysisCriterion expectancy = new ExpectancyCriterion();
        AnalysisCriterion sqn = new SqnCriterion(); // System Quality Number (higher = better)
        AnalysisCriterion versusEnterAndHoldCriterion = new VersusEnterAndHoldCriterion(new NetReturnCriterion());

        Num romadValue = romad.calculate(series, tradingRecord);
        Num maxDrawdownValue = maxDrawdown.calculate(series, tradingRecord);
        Num expectancyValue = expectancy.calculate(series, tradingRecord);
        Num sqnValue = sqn.calculate(series, tradingRecord);
        Num vsBuyHold = versusEnterAndHoldCriterion.calculate(series, tradingRecord);

        // Display comprehensive results
        System.out.println("   Basic Metrics:");
        System.out.printf("      Total Positions:     %d%n", tradingRecord.getPositionCount());
        System.out.printf("      Net Return:          %.2f%%%n",
                netReturnValue.multipliedBy(series.numFactory().numOf(100)).doubleValue());
        System.out.printf("      Win Rate:            %.1f%%%n",
                winRate.multipliedBy(series.numFactory().numOf(100)).doubleValue());
        System.out.println();
        System.out.println("   Risk Metrics:");
        System.out.printf("      Maximum Drawdown:    %.2f%%%n",
                maxDrawdownValue.multipliedBy(series.numFactory().numOf(100)).doubleValue());
        System.out.printf("      Return/Max Drawdown: %.2f%n", romadValue.doubleValue());
        System.out.println();
        System.out.println("   Advanced Metrics:");
        System.out.printf("      Expectancy:          %.4f (avg profit per trade)%n", expectancyValue.doubleValue());
        System.out.printf("      SQN (System Quality): %.2f (higher = better)%n", sqnValue.doubleValue());
        System.out.printf("      vs Buy & Hold:       %.2f%%%n",
                vsBuyHold.multipliedBy(series.numFactory().numOf(100)).doubleValue());
        System.out.println();

        // Step 6: Visualize the strategy with multiple subcharts
        System.out.println("[6/7] Generating comprehensive strategy visualization...");
        boolean isHeadless = GraphicsEnvironment.isHeadless();

        if (isHeadless) {
            System.out.println("   [WARN] Headless environment detected - skipping chart display");
            System.out.println("   [TIP] Run in a GUI environment to see interactive charts!");
        } else {
            try {
                ChartWorkflow chartWorkflow = new ChartWorkflow();
                JFreeChart chart = chartWorkflow.builder()
                        .withTitle("Bollinger Bands Mean Reversion Strategy - Yahoo Finance Data (AAPL)")
                        .withSeries(series) // Price bars (candlesticks)
                        .withTradingRecordOverlay(tradingRecord) // Trading positions marked on price chart
                        .withIndicatorOverlay(bbMiddle) // Middle band overlay
                        .withIndicatorOverlay(bbUpper) // Upper band overlay
                        .withIndicatorOverlay(bbLower) // Lower band overlay
                        .withSubChart(rsi) // RSI in first subchart
                        .withSubChart(obv) // OBV in second subchart
                        .withSubChart(new NetProfitLossCriterion(), tradingRecord) // Net profit/loss in third subchart
                        .toChart();

                chartWorkflow.displayChart(chart, "ta4j Yahoo Finance Backtest - Advanced Mean Reversion Strategy");
                System.out.println("   [OK] Multi-subchart displayed in new window");
                System.out.println("   [TIP] Chart shows: Price with BB bands, RSI, OBV, and P&L");
            } catch (Exception ex) {
                LOG.warn("Failed to display chart: {}", ex.getMessage(), ex);
                System.out.println("   [WARN] Could not display chart: " + ex.getMessage());
            }
        }
        System.out.println();

        // Step 7: Explain advanced concepts
        System.out.println("[7/7] Advanced Concepts Demonstrated");
        System.out.println("   ──────────────────────────────────────────");
        System.out.println("   ✓ Bollinger Bands: Mean reversion indicator");
        System.out.println("     - Price tends to revert to the middle band");
        System.out.println("     - Lower band = oversold, Upper band = overbought");
        System.out.println();
        System.out.println("   ✓ ATR-based Stop Loss: Dynamic risk management");
        System.out.println("     - Adapts to market volatility automatically");
        System.out.println("     - Tighter stops in calm markets, wider in volatile markets");
        System.out.println();
        System.out.println("   ✓ Multi-Indicator Confirmation: Reduces false signals");
        System.out.println("     - RSI confirms oversold/overbought conditions");
        System.out.println("     - OBV confirms volume support for price moves");
        System.out.println();
        System.out.println("   ✓ Advanced Metrics: Deeper performance insights");
        System.out.println("     - Expectancy: Average profit per trade");
        System.out.println("     - SQN: System Quality Number (risk-adjusted performance)");
        System.out.println("     - Maximum Drawdown: Largest peak-to-trough decline");
        System.out.println();

        // Summary
        System.out.println("╔══════════════════════════════════════════════════════════════╗");
        System.out.println("║                         Summary                              ║");
        System.out.println("╚══════════════════════════════════════════════════════════════╝");
        System.out.println();
        System.out.println("What just happened?");
        System.out.println();
        System.out.println("   1. Loaded 2 years of daily OHLCV data for AAPL from Yahoo Finance");
        System.out.println("   2. Created advanced indicators:");
        System.out.println("      - Bollinger Bands (mean reversion)");
        System.out.println("      - RSI (momentum confirmation)");
        System.out.println("      - ATR (volatility for dynamic stops)");
        System.out.println("      - OBV (volume trend confirmation)");
        System.out.println("      - Custom price-to-middle-band ratio (indicator composition)");
        System.out.println("   3. Built a sophisticated mean reversion strategy:");
        System.out.println("      - Entry: Price at/below lower BB + RSI < 45 + (OBV rising OR price below middle)");
        System.out.println("      - Exit: Price at/above upper BB OR RSI > 65 OR ATR stop (2.5x ATR)");
        System.out.println("   4. Backtested with ATR-based dynamic stop-loss (adapts to volatility)");
        System.out.println("   5. Analyzed with advanced metrics (Expectancy, SQN, Max Drawdown)");
        if (!isHeadless) {
            System.out.println("   6. Visualized with multi-subchart (Price, RSI, OBV, P&L)");
        }
        System.out.println();
        System.out.println("Advanced Features Demonstrated:");
        System.out.println("   ✓ Bollinger Bands for mean reversion trading");
        System.out.println("   ✓ ATR-based dynamic stop-loss (better than fixed %)");
        System.out.println("   ✓ Multi-indicator confirmation (reduces false signals)");
        System.out.println("   ✓ Indicator composition (BinaryOperationIndicator)");
        System.out.println("   ✓ Advanced performance metrics (Expectancy, SQN)");
        System.out.println("   ✓ Multi-subchart visualization");
        System.out.println();
        System.out.println("Yahoo Finance Data Source Features:");
        System.out.println("   - Load data by number of days: loadSeries(\"AAPL\", 730)");
        System.out.println("   - Load data by bar count: loadSeries(\"AAPL\", DAY_1, 500)");
        System.out.println("   - Load data by date range: loadSeries(\"AAPL\", DAY_1, start, end)");
        System.out.println("   - Supports multiple intervals: 1m, 5m, 15m, 30m, 1h, 4h, 1d, 1wk, 1mo");
        System.out.println("   - Works with stocks, ETFs, and cryptocurrencies");
        System.out.println("   - Automatic pagination for large date ranges");
        System.out.println();
        System.out.println("Next Steps - Experiment with:");
        System.out.println("   - Different tickers: \"MSFT\", \"GOOGL\", \"BTC-USD\", \"ETH-USD\"");
        System.out.println("   - Adjust BB period (try 10, 30) and multiplier (try 1.5, 2.5)");
        System.out.println("   - Modify RSI thresholds (try 30/70 or 35/65)");
        System.out.println("   - Change ATR multiplier for stops (try 1.5x or 3.0x)");
        System.out.println("   - Add MACD or Stochastic for additional confirmation");
        System.out.println("   - Try different intervals: HOUR_1, WEEK_1 for different timeframes");
        System.out.println("   - Explore other examples in ta4j-examples");
        System.out.println("   - Check out the wiki: https://ta4j.github.io/ta4j-wiki/");
        System.out.println();
        System.out.println("Your turn! Modify this code and see how it affects performance.");
        System.out.println();
    }
}
